/**
 *
 * Copyright (C) 2012 João Francisco Biondo Trinca
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 *
 * @author     João Francisco Biondo Trinca <wolfulus@gmail.com>
 * @copyright  2012 João Francisco Biondo Trinca <wolfulus@gmail.com>
 * @license    http://www.opensource.org/licenses/mit-license.html  MIT License
 *
 */

var util = require('util');
var events = require('events');

var extend = require('extend');

var http = require('http');
var httpProxy = require('http-proxy');

/**
 * Classes
 */
var Adapter = module.exports.Adapter = require('./adapter');

/**
 *
 * @param {Adapter} adapter
 * @param {Array} options
 * @returns {Proxy}
 * @constructor
 */
var Proxy = function(adapter, options) {

    events.EventEmitter.call(this);

    var self = this;

    if (!adapter) {
        throw new Error("Invalid adapter.");
    }

    this.adapter = adapter;
    this.table = [];

    this.proxy = new httpProxy.RoutingProxy();

    this.proxy.on('end', function(req, res, response) {
        var host = req.headers.host;
        var input = response.socket._bytesDispatched;
        var output = response.socket.bytesRead;
        self.emit('bandwidth', host, input, output);
    });

    this.proxy.on('error', function(error) {
        console.error("Failed to proxy request - ", error);
    });

    if (options) {
        this.options = extend(this.options, options);
        if (options.refresh && options.interval) {
            setInterval(function() {
                try {
                    self.refresh();
                } catch(error) {
                    console.error("Failed to refresh routing table - ", error);
                }
            }, options.interval);
        }
    } else {
        this.options = {};
    }

    this.http = null;

    this.adapter.init();
    this.refresh();

    return this;
};

util.inherits(Proxy, events.EventEmitter);

/**
 * Error response
 * @param request
 * @param response
 * @private
 */
Proxy.prototype._error = function(request, response, websocket) {
    if (!this.emit('error', request, response, websocket)) {
        response.writeHead('404');
        response.write("Route missing.");
    }
    try {
        response.end();
    } catch(e) {
    }
};

/**
 * Refreshes the routing table
 */
Proxy.prototype.refresh = function() {
    this.table = this.adapter.fetch();
};

/**
 * Runs the proxy
 * @param port
 */
Proxy.prototype.run = function(port) {

    var self = this;

    this.http = http.createServer(function (request, response) {
        // checks for route existence
        if (self.table[request.headers.host] != null) {
            var route = self.table[request.headers.host];
            if (route && route.host && route.port) {
                // redirects the request
                self.proxy.proxyRequest(request, response, {
                    host: route.host,
                    port: route.port
                });
            } else {
                console.error("Invalid route properties for host '" + request.headers.host + "' -- missing host or port");
                self._error(request, response, false);
            }
        } else {
            console.error("Route not found for host '" + request.headers.host + "'");
            self._error(request, response, false);
        }
    });

    this.http.on('upgrade', function(request, socket, head) {
        // searches the specified route
        if (self.table[request.headers.host] != null) {
            var route = self.table[reques.headers.host];
            if (route.host && route.port) {
                // redirects the request
                self.proxy.proxyWebSocketRequest(request, socket, head, {
                    host: route.host,
                    port: route.port
                });
            } else {
                console.error("Invalid route properties for host '" + request.headers.host + "' -- missing host or port");
                self._error(request, response, true);
            }
        } else {
            console.error("Route not found for host '" + request.headers.host + "'");
            self._error(request, response, true);
        }
    });

    this.http.listen(port);

};

/**
 * Proxy export
 */
module.exports.Proxy = Proxy;